﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using DanielVaughan.Logging;
using System.Threading;
using System.Collections.Generic;
using System.Diagnostics;
using SilverlightDatabase.Exceptions;

namespace SilverlightDatabase
{
    public class DatabaseSession : IDisposable
    {
        private Database database;
        private bool isOpen;
        private string databaseName;
        private ILog log;
        private static object _lockCookie = new object();
        private static Dictionary<int, LockedThreadInfo> lockedThreads = new Dictionary<int, LockedThreadInfo>();
        public event OnOpenExceptionHandler OnOpenException;

        static DatabaseSession()
        {
            lockedThreads = new Dictionary<int, LockedThreadInfo>();
        }

        protected DatabaseSession(string databaseName, ILog log)
        {
            this.log = log;
            this.databaseName = databaseName;
        }

        public static DatabaseSession Open(string databaseName, ILog log, OnOpenExceptionHandler exceptionHandler)
        {
            Lock();

            var session = new DatabaseSession(databaseName, log);

            session.OnOpenException += exceptionHandler;

            return session;
        }

        public static void AddSessionInfo(DatabaseSession session)
        {
            var thread = Thread.CurrentThread;
            var threadInfo = lockedThreads[thread.ManagedThreadId];

            threadInfo.Session = session;
        }

        public static void Lock()
        {
            var thread = Thread.CurrentThread;
            var stackTrace = new StackTrace();

            Debug.WriteLine("********************************************** Locking thread '{0}'", thread.Name);

            if (lockedThreads.ContainsKey(thread.ManagedThreadId))
            {
                Debugger.Break();
            }

            Monitor.Enter(_lockCookie);

            Debug.WriteLine("********************************************** Thread locked for '{0}'", thread.Name);

            lockedThreads.Add(thread.ManagedThreadId, new LockedThreadInfo(thread, stackTrace.ToString())); 
        }

        public static void Unlock()
        {
            var thread = Thread.CurrentThread;

            lockedThreads.Remove(thread.ManagedThreadId);

            Debug.WriteLine("********************************************** Unlocked thread for '{0}'", thread.Name);

            Monitor.Exit(_lockCookie);
        }

        public string DatabaseName
        {
            get 
            { 
                return databaseName; 
            }
        }

        public Database Database
        {
            get
            {
                if (database == null)
                {
                    if (!Database.DoesDatabaseExists(databaseName))
                    {
                        // log.InfoFormat("Database '{0}' does not exist.  Creating new.", databaseName);

                        database = Database.CreateDatabase(databaseName, new byte[0], true, true, log);
                        database.GrowthSize = (int)(2 * Math.Pow(2, 10)); // 2MB

                        database.Save();
                    }
                }

                if (!isOpen)
                {
                    var exceptionHandler = new OnOpenExceptionHandler(OnOpenException);

                    // log.InfoFormat("Database '{0}' exists.  Opening.", databaseName);

                    database = Database.OpenDatabase(databaseName, new byte[0], true, log, exceptionHandler);
                    isOpen = true;
                }

                database.Log = log;

                return database;
            }
        }

        public Table<T> GetTable<T>()
        {
            if (!this.Database.DoesTableExists(typeof(T)))
            {
                // log.InfoFormat("Table of type '{0}' does not exist.  Creating new.", typeof(T).FullName);

                this.Database.CreateTable<T>();
            }

            var table = this.Database.Table<T>();

            return table;
        }

        public void Close()
        {
            if (database != null && database.Dirty)
            {
                // log.InfoFormat("Database saving changes.");

                database.Save();

                // log.InfoFormat("Database closing.");
            }

            isOpen = false;
        }

        public void Dispose()
        {
            this.Close();

            Unlock();
        }
    }
}
